//
//  PMDampedSpring.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 12/23/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

/**
 * A subclass of PMConstraint for representing damped springs. PMDampedSprings 
 * provide flexible binding of two PMBody objects. PMDampedSprings are defined 
 * by three properties: the length of the spring when at rest, the stiffness of
 * the spring, which is the spring constant, and the damping applied to generated
 * spring forces.
 */

#import "PMDampedSpring.h"
#import "PMBody.h"


@implementation PMDampedSpring

#pragma mark -
#pragma mark Init

/**
 * Returns a PMDampedSpring object initialized with a given rest length, stiffness,  
 * and damping, anchoring two given rigid bodies at their center of gravity. 
 */
- (id)initWithBodyA:(PMBody *)aBody bodyB:(PMBody *)otherBody restLength:(PMFloat)restLength stiffness:(PMFloat)stiffness damping:(PMFloat)damping {
	return [self initWithAnchor:PMVectZero 
						onBodyA:aBody 
						 anchor:PMVectZero 
						onBodyB:otherBody 
					 restLength:restLength 
					  stiffness:stiffness 
						damping:damping];
}

/**
 * Returns a PMDampedSpring object initialized with a given rest length, stiffness,  
 * and damping, anchoring two given rigid bodies at given positions reletive to 
 * the corresponding body's center of gravity. 
 */
- (id)initWithAnchor:(PMAnchor)anchorA onBodyA:(PMBody *)aBody anchor:(PMAnchor)anchorB onBodyB:(PMBody *)otherBody restLength:(PMFloat)restLength stiffness:(PMFloat)stiffness damping:(PMFloat)damping {
	if (![super init])
		return nil;
	
	// allocate and initialize cpConstraint
	constraint = cpDampedSpringNew(aBody.cpBody, otherBody.cpBody, 
								   anchorA, anchorB, 
								   restLength, stiffness, damping);
	
	// set body references
	self.bodyA = aBody;
	self.bodyB = otherBody;
	
	// reference this PMConstraint object with cpConstraint's data pointer
	// this is for internal use
	constraint->data = self;
	
	return self;
}


#pragma mark Convenience

/**
 * Creates and returns a PMDampedSpring object initialized with a given rest length, 
 * stiffness, and damping, anchoring two given rigid bodies at their center of 
 * gravity.
 */
+ (PMDampedSpring *)dampedSpringWithBodyA:(PMBody *)aBody bodyB:(PMBody *)otherBody restLength:(PMFloat)restLength stiffness:(PMFloat)stiffness damping:(PMFloat)damping {
	return [[[PMDampedSpring alloc] initWithAnchor:PMVectZero 
										   onBodyA:aBody 
											anchor:PMVectZero 
										   onBodyB:otherBody 
										restLength:restLength 
										 stiffness:stiffness 
										   damping:damping] autorelease];
}

/**
 * Creates and returns a PMDampedSpring object initialized with a given rest length, 
 * stiffness, and damping, anchoring two given rigid bodies at given positions 
 * relative to the corresponding body's center of gravity. 
 */
+ (PMDampedSpring *)dampedSpringWithAnchor:(PMAnchor)anchorA onBodyA:(PMBody *)aBody anchor:(PMAnchor)anchorB onBodyB:(PMBody *)otherBody restLength:(PMFloat)restLength stiffness:(PMFloat)stiffness damping:(PMFloat)damping {
	return [[[PMDampedSpring alloc] initWithAnchor:anchorA 
										   onBodyA:aBody 
											anchor:anchorB 
										   onBodyB:otherBody 
										restLength:restLength 
										 stiffness:stiffness 
										   damping:damping] autorelease];
} 


#pragma mark -
#pragma mark Getters

/**
 * Returns the receiver's spring anchor position for bodyA.
 */
- (PMAnchor)anchorA {
	return cpDampedSpringGetAnchr1(constraint);
}

/**
 * Returns the receiver's spring anchor position for bodyB.
 */
- (PMAnchor)anchorB {
	return cpDampedSpringGetAnchr2(constraint);
}

/**
 * Returns the receiver's spring anchor position for the body corresponding to 
 * the given key. Returns PMVectZero if there is no body/anchor for the given key.
 */
- (PMAnchor)anchorForBodyWithKey:(PMConstraintBodyKey)bodyKey {
	switch (bodyKey) {
		case PMConstraintBodyKeyA:
			return [self anchorA];
		case PMConstraintBodyKeyB:
			return [self anchorB];
		default:
			return PMVectZero;
	}
}

/**
 * Returns the length of the receiver when at rest.
 */
- (PMFloat)restLength {
	return cpDampedSpringGetRestLength(constraint);
}

/**
 * Returns the receiver's spring constant. This is the same as the receiver's 
 * stiffness.
 */
- (PMFloat)springConst {
	return [self stiffness];
}

/**
 * Returns the receiver's stiffness. This is the same as the receivers spring
 * constant.
 */
- (PMFloat)stiffness {
	return cpDampedSpringGetStiffness(constraint);
}

/**
 * Returns the amount of damping applied to the receiver.
 */
- (PMFloat)damping {
	return cpDampedSpringGetDamping(constraint);
}


#pragma mark -
#pragma mark Setters

/**
 * Sets the spring anchor position of the receiver's bodyA to a given point relative
 * the the body's center of gravity.
 */
- (void)setAnchorA:(PMAnchor)anchor {
	cpPinJointSetAnchr1(constraint, anchor);
}

/**
 * Sets the spring anchor position of the receiver's bodyB to a given point relative
 * the the body's center of gravity.
 */
- (void)setAnchorB:(PMAnchor)anchor {
	cpPinJointSetAnchr2(constraint, anchor);
}

/**
 * Sets the spring anchor position of the receiver's body corresponding to the 
 * given key, to a given point relative the the body's center of gravity.
 */
- (void)setAnchor:(PMAnchor)anchor forBodyWithKey:(PMConstraintBodyKey)bodyKey {
	switch (bodyKey) {
		case PMConstraintBodyKeyA:
			return [self setAnchorA:anchor];
		case PMConstraintBodyKeyB:
			return [self setAnchorB:anchor];
	}
}

/**
 * Sets the length of the receiver when at rest to a given value.
 */
- (void)setRestLength:(PMFloat)restLength {
	cpDampedSpringSetRestLength(constraint, restLength);
}	

/**
 * Sets the receiver's spring constant to a given value. This is the same as
 * setting the receiver's stiffness.
 */
- (void)setSpringConst:(PMFloat)springConst {
	[self setStiffness:springConst];
}

/**
 * Sets the receiver's stiffness to a given value. This is the same as setting
 * the receiver's spring constant.
 */
- (void)setStiffness:(PMFloat)stiffness {
	cpDampedSpringSetStiffness(constraint, stiffness);
}

/**
 * Sets the amount of damping applied to the receiver to a given value.
 */
- (void)setDamping:(PMFloat)damping {
	cpDampedSpringSetDamping(constraint, damping);
}

@end
